﻿'TODO: Document Rotated Label
Imports System.Drawing.Drawing2D
Imports System.Drawing
Imports System.ComponentModel
Imports System.ComponentModel.Design


<DefaultEvent("Click"), ToolboxBitmap(GetType(RotatedLabel), "RotatedLabel")> _
Public Class RotatedLabel

#Region "Default Values"
    Private Const def_Text As String = "Label Text"
    Private Const def_TextColor As String = "Black"
    Private Const def_TextAlignment As String = "MiddleCenter"
    Private Const def_LabelOrientation As String = "0"

    Private Const def_Height As Integer = 25
    Private Const def_Width As Integer = 120

    Private Const def_AutoSize As Boolean = True
#End Region

#Region "Private Attributes"
    Private WithEvents _Background As New BackgroundType(Me)

    Private _TextAlignment As ContentAlignment = CType([Enum].Parse(GetType(ContentAlignment), def_TextAlignment), ContentAlignment)
    Private _LabelOrientation As LabelOrientations = CType([Enum].Parse(GetType(LabelOrientations), def_LabelOrientation), LabelOrientations)

    Private _Height As Integer = def_Height
    Private _Width As Integer = def_Width

    Private _AutoSize As Boolean = def_AutoSize
#End Region

#Region "Transparency Support"
    Private Const WS_EX_TRANSPARENT As Int16 = &H20

    Friend Sub SetTransparencyMode(ByVal IsOn As Boolean)
        If IsOn Then
            'Support transparency
            Me.SetStyle(ControlStyles.SupportsTransparentBackColor, True)
            Me.SetStyle(ControlStyles.Opaque, True)
        Else
            'No transparency support
            Me.SetStyle(ControlStyles.SupportsTransparentBackColor, False)
            Me.SetStyle(ControlStyles.Opaque, False)
        End If
        If Me.Parent Is Nothing Then
            Me.Refresh()
        Else
            Me.Parent.Refresh()
        End If
    End Sub
    Protected Overrides ReadOnly Property CreateParams() As System.Windows.Forms.CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            cp.ExStyle = cp.ExStyle Or WS_EX_TRANSPARENT
            Return cp
        End Get
    End Property
#End Region

#Region "Public Enumerations"
    Public Enum LabelOrientations
        Angle0 = 0
        Angle90 = 90
        Angle180 = 180
        Angle270 = 270
    End Enum
#End Region

#Region "Hidden Properties"
    <Browsable(False)> _
    Shadows Property BackColor() As Color
        Get
        End Get
        Set(ByVal value As Color)
        End Set
    End Property
    <Browsable(False)> _
    Shadows Property BackgroundImage() As Color
        Get
        End Get
        Set(ByVal value As Color)
        End Set
    End Property
    <Browsable(False)> _
    Shadows Property BackgroundImageLayout() As Color
        Get
        End Get
        Set(ByVal value As Color)
        End Set
    End Property
#End Region

#Region "Public Interface"
    Public Sub New()
        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
        Me.SetStyle(ControlStyles.ResizeRedraw, True)
        Me.SetStyle(ControlStyles.UserPaint, True)
        SetTransparencyMode(False)
        Me.Size = New Size(def_Width, def_Height)
        Me.Padding = New Padding(5, 5, 5, 5)
        Me.Invalidate()
    End Sub

    Shadows Property Text() As String
        Get
            Return MyBase.Text
        End Get
        Set(ByVal value As String)
            MyBase.Text = value
            Me.Invalidate()
        End Set
    End Property
    Shadows Sub ResetText()
        MyBase.Text = Nothing
    End Sub
    Shadows Function ShouldSerializeText() As Boolean
        Return Not (MyBase.Text.Equals(Nothing))
    End Function


    <CategoryAttribute("Layout"), DefaultValue(def_AutoSize), _
        System.ComponentModel.Browsable(True), EditorBrowsable(EditorBrowsableState.Always), _
        DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), _
        DescriptionAttribute("Specifies whether the label will automatically size itself to fit its contents.")> _
    Shadows Property AutoSize() As Boolean
        Get
            Return _AutoSize
        End Get
        Set(ByVal value As Boolean)
            _AutoSize = value
            Refresh()
        End Set
    End Property
    Public Shadows Sub ResetAutoSize()
        Me.AutoSize = def_AutoSize
    End Sub
    Public Shadows Function ShouldSerializeAutoSize() As Boolean
        Return Not (_AutoSize.Equals(def_AutoSize))
    End Function

    <Category("Appearance"), _
        Description("Sets the Text alignment of the rotated label."), _
        DefaultValue(GetType(ContentAlignment), def_TextAlignment)> _
    Public Property TextAlignment() As ContentAlignment
        Get
            Return _TextAlignment
        End Get
        Set(ByVal value As ContentAlignment)
            _TextAlignment = value
            Me.Refresh()
        End Set
    End Property

    <Category("Appearance"), TypeConverter(GetType(BackgroundTypeConverter))> _
    Public Property Background() As BackgroundType
        Get
            Return _Background()
        End Get
        Set(ByVal value As BackgroundType)
            _Background = value
            Me.Invalidate()
        End Set
    End Property
    Public Sub ResetBackground()
        _Background.Reset()
    End Sub
    Public Function ShouldSerializeBackground() As Boolean
        Return _Background.ShouldSerialize()
    End Function


    <Category("Appearance"), _
      Description("Sets the rotation angle of the label."), _
      DefaultValue(GetType(LabelOrientations), def_LabelOrientation), _
      RefreshProperties(RefreshProperties.All), _
      BrowsableAttribute(True)> _
  Public Property LabelOrientation() As LabelOrientations
        Get
            Return _LabelOrientation
        End Get
        Set(ByVal value As LabelOrientations)
            _LabelOrientation = value
            Me.Refresh()
            'End If
        End Set
    End Property

#End Region

#Region "Overrides"
    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(e)
        ReDraw(e.Graphics)
    End Sub
    Protected Overrides Sub OnFontChanged(ByVal e As System.EventArgs)
        MyBase.OnFontChanged(e)
        Invalidate()
    End Sub
    Protected Overrides Sub OnForeColorChanged(ByVal e As System.EventArgs)
        MyBase.OnForeColorChanged(e)
        Invalidate()
    End Sub
    Protected Overrides Sub OnPaddingChanged(ByVal e As System.EventArgs)
        MyBase.OnPaddingChanged(e)
        Invalidate()
    End Sub
#End Region

#Region "Private Methods"
    Private Sub DrawControlBackground(ByRef G As Graphics, ByVal Rect As RectangleF)
        If _Background.FillStyle = BackgroundType.FillStyles.Gradient Then
            Dim myBrush As LinearGradientBrush
            myBrush = New LinearGradientBrush(Me.ClientRectangle, _Background.BackColor1, _Background.BackColor2, Me._Background.FillGradientStyle)
            G.FillRectangle(myBrush, Rect)
        Else
            G.FillRectangle(New SolidBrush(_Background.BackColor1), Rect)
        End If
    End Sub
    Private Sub ReDraw(ByRef myG As Graphics)
        Dim TextHeight As Single = myG.MeasureString(Me.Text, Me.Font).Height
        Dim TextWidth As Single = myG.MeasureString(Me.Text, Me.Font).Width

        Dim CtlHeight As Integer
        Dim CtlWidth As Integer
        If _AutoSize Then
            'Calculate new bounds
            Select Case _LabelOrientation
                Case LabelOrientations.Angle0, LabelOrientations.Angle180
                    'Horizontal Label
                    CtlWidth = CInt(Math.Ceiling(TextWidth + Me.Padding.Left + Me.Padding.Right))
                    CtlHeight = CInt(Math.Ceiling(TextHeight + Me.Padding.Top + Me.Padding.Bottom))
                Case LabelOrientations.Angle90, LabelOrientations.Angle270
                    'Vertical Label
                    CtlWidth = CInt(Math.Ceiling(TextHeight + Me.Padding.Top + Me.Padding.Bottom))
                    CtlHeight = CInt(Math.Ceiling(TextWidth + Me.Padding.Left + Me.Padding.Right))
            End Select
            Me.ClientSize = New Size(CtlWidth, CtlHeight)
        Else
            'Use current control client size
            CtlWidth = Me.ClientSize.Width
            CtlHeight = Me.ClientSize.Height
        End If

        'Calculate X and Y offsets for label text alignment
        Dim TextOffset As New PointF(0, 0)
        If AutoSize Then
            TextOffset = New Point(Me.Padding.Left, Me.Padding.Top)
        Else
            Dim RotatedTextHeight As Single
            Dim RotatedTextWidth As Single
            Select Case LabelOrientation
                Case LabelOrientations.Angle0, LabelOrientations.Angle180
                    RotatedTextWidth = TextWidth
                    RotatedTextHeight = TextHeight
                Case LabelOrientations.Angle90, LabelOrientations.Angle270
                    RotatedTextWidth = TextHeight
                    RotatedTextHeight = TextWidth
            End Select

            Dim X As Single = 0
            Dim Y As Single = 0
            Select Case Me.TextAlignment
                Case ContentAlignment.TopLeft
                    X = Me.Padding.Top
                    Y = 0
                Case ContentAlignment.TopCenter
                    Y = Me.Padding.Top
                    X = Math.Max(Me.Padding.Left, Me.Padding.Left + CSng(CtlWidth - Me.Padding.Left - Me.Padding.Right - RotatedTextWidth) / 2.0!)
                Case ContentAlignment.TopRight
                    X = Math.Max(Me.Padding.Left, Me.Padding.Left + CSng(CtlWidth - Me.Padding.Left - Me.Padding.Right - RotatedTextWidth))
                    Y = Me.Padding.Top
                Case ContentAlignment.MiddleLeft
                    X = Me.Padding.Left
                    Y = Math.Max(Me.Padding.Top, Me.Padding.Top + CSng(CtlHeight - Me.Padding.Top - Me.Padding.Top - RotatedTextHeight) / 2.0!)
                Case ContentAlignment.MiddleRight
                    X = Math.Max(Me.Padding.Left, Me.Padding.Left + CSng(CtlWidth - Me.Padding.Left - Me.Padding.Right - RotatedTextWidth))
                    Y = Math.Max(Me.Padding.Top, Me.Padding.Top + CSng(CtlHeight - Me.Padding.Top - Me.Padding.Top - RotatedTextHeight) / 2.0!)
                Case ContentAlignment.MiddleCenter
                    X = Math.Max(Me.Padding.Left, Me.Padding.Left + CSng(CtlWidth - Me.Padding.Left - Me.Padding.Right - RotatedTextWidth) / 2.0!)
                    Y = Math.Max(Me.Padding.Top, Me.Padding.Top + CSng(CtlHeight - Me.Padding.Top - Me.Padding.Top - RotatedTextHeight) / 2.0!)
                Case ContentAlignment.BottomLeft
                    X = CtlHeight - Me.Padding.Bottom
                    Y = Math.Max(Me.Padding.Top, Me.Padding.Top + CSng(CtlHeight - Me.Padding.Top - Me.Padding.Bottom - RotatedTextHeight))
                Case ContentAlignment.BottomCenter
                    X = Math.Max(Me.Padding.Left, Me.Padding.Left + CSng(CtlWidth - Me.Padding.Left - Me.Padding.Right - RotatedTextWidth) / 2.0!)
                    Y = Math.Max(Me.Padding.Top, Me.Padding.Top + CSng(CtlHeight - Me.Padding.Top - Me.Padding.Bottom - RotatedTextHeight))
                Case ContentAlignment.BottomRight
                    X = Math.Max(Me.Padding.Left, Me.Padding.Left + CSng(CtlWidth - Me.Padding.Left - Me.Padding.Right - RotatedTextWidth))
                    Y = Math.Max(Me.Padding.Top, Me.Padding.Top + CSng(CtlHeight - Me.Padding.Top - Me.Padding.Bottom - RotatedTextHeight))
            End Select
            Select Case LabelOrientation
                Case LabelOrientations.Angle0, LabelOrientations.Angle180
                    TextOffset = New PointF(X, Y)
                Case LabelOrientations.Angle90, LabelOrientations.Angle270
                    TextOffset = New PointF(Y, X)
            End Select
        End If

        myG.SmoothingMode = SmoothingMode.None

        'Draw Gradient Fill/Solid Fill/Transparent background
        'Select Case Me.FillStyle
        '    Case FillStyles.Transparent
        '        myG.FillRectangle(New SolidBrush(Color.Transparent), Me.ClientRectangle)
        '    Case FillStyles.Solid
        '        myG.FillRectangle(New SolidBrush(Me.FillBackColor0), Me.ClientRectangle)
        '    Case FillStyles.Gradient
        '        Dim FillRectBrush As New RectangleF(0, 0, CtlWidth, CtlHeight)
        '        Dim myFillBrush As New LinearGradientBrush(FillRectBrush, Me.FillBackColor0, Me.FillBackColor1, Me.FillGradientOrientation)
        '        myG.FillRectangle(myFillBrush, Me.ClientRectangle)
        'End Select

        DrawControlBackground(myG, Me.ClientRectangle)


        'Rotate/Translate Graphics area prior to writing out label Text
        myG.RotateTransform(_LabelOrientation)
        Select Case _LabelOrientation
            Case LabelOrientations.Angle0
                'No translation necessary
            Case LabelOrientations.Angle90
                myG.TranslateTransform(0, -CtlWidth)
            Case LabelOrientations.Angle180
                myG.TranslateTransform(-CtlWidth, -CtlHeight)
            Case LabelOrientations.Angle270
                myG.TranslateTransform(-CtlHeight, 0)
        End Select


        'Draw Text (using Text alignment options)
        Dim myTextBrush As Brush = New SolidBrush(Me.ForeColor)
        If _Background.BackColor1 = Color.Transparent AndAlso _Background.FillStyle = BackgroundType.FillStyles.Solid Then
            myG.TextRenderingHint = Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit
        Else
            myG.TextRenderingHint = Drawing.Text.TextRenderingHint.ClearTypeGridFit
        End If

        myG.DrawString(Me.Text, Me.Font, myTextBrush, TextOffset.X, TextOffset.Y)

        myG.ResetTransform()
        myG.Dispose()
    End Sub
#End Region

#Region "Private Event Handlers"
    Private Sub RotatedLabel_ForeColorChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.ForeColorChanged
        Me.Refresh()
    End Sub

    Private Sub RotatedLabel_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.TextChanged
        Me.Refresh()
    End Sub

    Private Sub _Background_PropertyChanged(ByVal PropertyName As String) Handles _Background.PropertyChanged
        Me.Invalidate()
    End Sub
#End Region

#Region "Background Type"
    Public Class BackgroundType
        'Defaults
        Private Const def_SupportTransparency As Boolean = False

        Private def_BackColor1 As Color = Color.Gold
        Private def_BackColor2 As Color = Color.DarkGoldenrod
        Private def_FillStyle As FillStyles = FillStyles.Gradient
        Private def_FillGradientStyle As LinearGradientMode = LinearGradientMode.Horizontal


        'Private Properties
        Private _SupportTransparency As Boolean = def_SupportTransparency

        Private _BackColor1 As Color = def_BackColor1
        Private _BackColor2 As Color = def_BackColor2
        Private _FillStyle As FillStyles = def_FillStyle
        Private _GradientOrientation As LinearGradientMode = def_FillGradientStyle

        Private _ParentCtl As RotatedLabel = Nothing


        'Enumerations
        Public Enum FillStyles As Integer
            Solid = 0
            Gradient = 1
        End Enum

        'Public Events
        Public Event PropertyChanged(ByVal PropertyName As String)

        'Public Interface
        Friend Sub New(ByVal Control As RotatedLabel)
            _ParentCtl = Control
        End Sub

        ''' <summary>
        ''' Defines the Background color of the gauge when <see cref="FillStyle"/> is set to <see cref="FillStyles">Solid</see>. Otherwise is used as the first color
        ''' in a color gradient with the end color determined by the <see cref="BackColor2"/> property.
        ''' </summary>
        ''' <value>The Background color of the gauge or the start gradient color of background.</value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        <Description("The background color of the control. Primary color if FillStyle is set to Solid or gradient start color if FillStyle is set to Gradient."), _
            BrowsableAttribute(True)> _
        Public Property BackColor1() As Color
            Get
                Return _BackColor1
            End Get
            Set(ByVal value As Color)
                _BackColor1 = value
                If _BackColor1 = Color.Transparent AndAlso _FillStyle = FillStyles.Solid Then
                    _ParentCtl.SetTransparencyMode(True)
                Else
                    _ParentCtl.SetTransparencyMode(False)
                End If
                RaiseEvent PropertyChanged("BackColor1")
            End Set
        End Property
        ''' <summary>
        ''' Resets the <see>BarGauge.BackColor1</see> property.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub ResetBackColor1()
            BackColor1 = def_BackColor1
        End Sub
        ''' <summary>
        ''' Indicates whether the default value of the property has changed from it's default in design mode and needs to be serialized.
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function ShouldSerializeBackColor1() As Boolean
            Return Not (BackColor1.Equals(def_BackColor1))
        End Function

        <Description("The secondary color of the control background when FillStyle is set to Gradient."), _
            BrowsableAttribute(True)> _
        Public Property BackColor2() As Color
            Get
                Return _BackColor2
            End Get
            Set(ByVal value As Color)
                _BackColor2 = value
                RaiseEvent PropertyChanged("BackColor2")
            End Set
        End Property
        Public Sub ResetBackColor2()
            BackColor2 = def_BackColor2
        End Sub
        Public Function ShouldSerializeBackColor2() As Boolean
            Return Not (BackColor2.Equals(def_BackColor2))
        End Function

        <Description("The FillStyle for the Background of this control."), _
                BrowsableAttribute(True)> _
        Public Property FillStyle() As FillStyles
            Get
                Return _FillStyle
            End Get
            Set(ByVal value As FillStyles)
                _FillStyle = value
                If _FillStyle = FillStyles.Solid AndAlso _BackColor1 = Color.Transparent Then
                    _ParentCtl.SetTransparencyMode(True)
                Else
                    _ParentCtl.SetTransparencyMode(False)
                End If
                RaiseEvent PropertyChanged("FillStyle")
            End Set
        End Property
        Public Sub ResetFillStyle()
            FillStyle = def_FillStyle
        End Sub
        Public Function ShouldSerializeFillStyle() As Boolean
            Return Not (FillStyle.Equals(def_FillStyle))
        End Function

        <Description("The gradient style of the control background when FillStyle is set to Gradient."), _
            BrowsableAttribute(True)> _
        Public Property FillGradientStyle() As LinearGradientMode
            Get
                Return _GradientOrientation
            End Get
            Set(ByVal value As LinearGradientMode)
                _GradientOrientation = value
                RaiseEvent PropertyChanged("FillGradientStyle")
            End Set
        End Property
        Public Sub ResetFillGradientStyle()
            FillGradientStyle = def_FillGradientStyle
        End Sub
        Public Function ShouldSerializeFillGradientStyle() As Boolean
            Return Not (FillGradientStyle.Equals(def_FillGradientStyle))
        End Function

        Public Sub Reset()
            _BackColor1 = def_BackColor1
            _BackColor2 = def_BackColor2
            _FillStyle = def_FillStyle
            _GradientOrientation = def_FillGradientStyle

            RaiseEvent PropertyChanged("All")
        End Sub
        Public Function ShouldSerialize() As Boolean
            If _BackColor1.Equals(def_BackColor1) _
                    AndAlso _BackColor2.Equals(def_BackColor2) _
                    AndAlso _FillStyle.Equals(def_FillStyle) _
                    AndAlso _GradientOrientation.Equals(def_FillGradientStyle) Then
                Return False
            Else
                Return True
            End If
        End Function

        Public Overrides Function ToString() As String
            Return "(Background)"
        End Function
    End Class
    Public Class BackgroundTypeConverter
        Inherits TypeConverter

        Public Overrides Function GetPropertiesSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
            Return True
        End Function
        Public Overrides Function GetProperties(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal value As Object, ByVal attributes() As System.Attribute) As System.ComponentModel.PropertyDescriptorCollection
            Return TypeDescriptor.GetProperties(GetType(BackgroundType))
        End Function
    End Class
#End Region

End Class





